package ticketingsystem;

import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    final static int times = 100000;
    final static int totaltimes = 10000;
    final static int routenum = 10;
    final static int coachnum = 10;
    final static int seatnum = 100;
    final static int stationnum = 20;
    final static int retpc = 10;
    final static int buypc = 30;
    final static int inqpc = 100;

    public static void main(String[] args) throws InterruptedException {
        for (int logt = 2; logt < 7; logt++) {
            int threadNum = 1 << logt;
            // int threadNum = 64;
            singleTest(threadNum, true);
        }
    }

    static void singleTest(int threadNum, boolean print){
        final TicketingDS tds = new TicketingDS(routenum, coachnum, seatnum, stationnum, threadNum);
        Static result = new Static();
        Thread[] threads = new Thread[threadNum];
        @SuppressWarnings("unchecked")
        FutureTask<Static>[] tasks = new FutureTask[threadNum];
        for (int i = 0; i < threadNum; i++) {
            // tasks[i] = new FutureTask<>(new Task(tds, totaltimes / threadNum + (i < totaltimes % threadNum ? 1 : 0)));
            tasks[i] = new FutureTask<>(new Task(tds, times));
            threads[i] = new Thread(tasks[i]);
        }
        long start = System.nanoTime();
        for (Thread t : threads) {
            t.start();
        }
        for (FutureTask<Static> t : tasks) {
            try {
                result.add(t.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        long end = System.nanoTime();
        if(print){
            // result.printRaw();
            result.print();
            // System.out.println(totaltimes * 1e6 / (end - start));
            System.out.println(times * threadNum * 1e6 / (end - start));
        }
        System.gc();
    }

    static class Task implements Callable<Static> {
        final int testnum;
        final TicketingDS tds;
        final Random rand;

        Task(TicketingDS tds, int testnum) {
            this.testnum = testnum;
            this.tds = tds;
            rand = new Random();
        }

        @Override
        public Static call() throws Exception {
            ArrayList<Ticket> soldTicket = new ArrayList<Ticket>();
            Ticket ticket = new Ticket();
            Static result = new Static();
            for (int i = 0; i < testnum; i++) {
                int sel = rand.nextInt(inqpc);
                if (0 <= sel && sel < retpc && soldTicket.size() > 0) { // return ticket
                    ticket = soldTicket.remove(rand.nextInt(soldTicket.size()));
                    assert ticket != null;

                    long preTime = System.nanoTime();
                    boolean r = tds.refundTicket(ticket);
                    long postTime = System.nanoTime();

                    assert r;
                    result.refund(postTime - preTime);
                } else if (retpc <= sel && sel < buypc) { // buy ticket
                    String passenger = "John Doe";
                    int route = rand.nextInt(routenum) + 1;
                    int departure = rand.nextInt(stationnum - 1) + 1;
                    int arrival = departure + rand.nextInt(stationnum - departure) + 1; // arrival is always greater
                    // than departure
                    long preTime = System.nanoTime();
                    ticket = tds.buyTicket(passenger, route, departure, arrival);
                    long postTime = System.nanoTime();

                    if (ticket != null) {
                        soldTicket.add(ticket);
                    }
                    result.buy(postTime - preTime);
                } else if (buypc <= sel && sel < inqpc) { // inquiry ticket

                    int route = rand.nextInt(routenum) + 1;
                    int departure = rand.nextInt(stationnum - 1) + 1;
                    int arrival = departure + rand.nextInt(stationnum - departure) + 1; // arrival is always greater
                    // than departure
                    long preTime = System.nanoTime();
                    tds.inquiry(route, departure, arrival);
                    long postTime = System.nanoTime();

                    result.inquiry(postTime - preTime);
                }
            }
            return result;
        }
    }

    static class Static {
        long tr = 0, tb = 0, ti = 0;
        int cr = 0, cb = 0, ci = 0;

        void refund(long time) {
            cr++;
            tr += time;
        }

        void buy(long time) {
            cb++;
            tb += time;
        }

        void inquiry(long time) {
            ci++;
            ti += time;
        }

        void add(Static o) {
            tr += o.tr;
            tb += o.tb;
            ti += o.ti;
            cr += o.cr;
            cb += o.cb;
            ci += o.ci;
        }

        void print() {
            System.out.printf("%f %f %f ", tr / 1e3 / cr, tb / 1e3 / cb, ti / 1e3 / ci);
        }

        void printRaw() {
            System.out.printf("%d %d %d %d %d %d ", cr, tr, cb, tb, ci, ti);
        }
    }
}
